package geom;

import java.awt.geom.Point2D;

import Util.Constants;

/**
 * This class represents a finite line segment, that is, the segment between two
 * points.
 * 
 * @author Li Xiaohui (lixiaohui@nus.edu.sg)
 */
public class Segment {
	private Vector2DDouble start;
	private Vector2DDouble end;

	// line equation: ax+by+c=0
	private double a, b, c;
	private double minx, maxx, miny, maxy;

	/**
	 * Construct a new segment from two given points.
	 */
	public Segment(Vector2DDouble start, Vector2DDouble end) {
		super();
		this.start = start;
		this.end = end;
		double xr0 = start.getX();
		double yr0 = start.getY();
		double xr1 = end.getX();
		double yr1 = end.getY();
		// compute line equation: ax+by+c=0
		double dx = xr0 - xr1;
		double dy = yr0 - yr1;
		double ddx = (dx > 0) ? dx : -dx;
		double ddy = (dy > 0) ? dy : -dy;
		if (ddx < Constants.EPS) {
			this.a = 1.0;
			this.b = 0.0;
			this.c = -xr0;
		} else {
			if (ddy < Constants.EPS) {
				this.a = 0.0;
				this.b = 1.0;
				this.c = -yr0;
			} else {
				// real case. Let b=1.0 -> y+ax+c=0
				this.a = (yr0 - yr1) / (xr1 - xr0);
				this.b = 1.0;
				this.c = -yr0 - this.a * xr0;
			}
		}
		this.minx = Math.min(this.start.getX(), this.end.getX());
		this.maxx = Math.max(this.start.getX(), this.end.getX());

		this.miny = Math.min(this.start.getY(), this.end.getY());
		this.maxy = Math.max(this.start.getY(), this.end.getY());
	}

	/**
	 * Gets the end point of this segment.
	 * 
	 */
	public Vector2DDouble getEnd() {
		return end;
	}

	/**
	 * Sets the end point of this segment.
	 */
	public void setEnd(Vector2DDouble end) {
		this.end = end;
	}

	/**
	 * Gets the start point of this segment.
	 * 
	 */
	public Vector2DDouble getStart() {
		return start;
	}

	/**
	 * Sets the start point for this segment.
	 * 
	 */
	public void setStart(Vector2DDouble start) {
		this.start = start;
	}

	/**
	 * Returns "[start, end]".
	 */
	@Override
	public String toString() {
		return "[" + start + ", " + end + "]";
	}

	/**
	 * Determines whether or not the specified point lies on this segment
	 * 
	 * @param point
	 *            the point to check
	 * @return <code>true</code> if the specified point lies on this segment,
	 *         <code>false</code> otherwise
	 */
	public boolean isOnSegment(Point2D point) {
		double x = point.getX();
		double y = point.getY();
		double val = this.a * x + this.b * y + this.c;
		if (val < 0.0) {
			val = -val;
		}
		if (val > Constants.EPS) {
			return false;
		}

		if ((x < (this.minx - Constants.EPS))
				|| (x > (this.maxx + Constants.EPS))) {
			return false;
		}
		if ((y < (this.miny - Constants.EPS))
				|| (y > (this.maxy + Constants.EPS))) {
			return false;
		}
		return true;
	}

	/**
	 * Determines whether or not this segment intersects with another segment
	 * 
	 * @param segment2
	 *            the segment to check
	 * @return <code>true</code> if the specified segment intersects with this
	 *         segment, <code>false</code> otherwise
	 */
	public boolean intersects(Segment segment2) {
		if (segment2 == null) {
			throw new IllegalArgumentException("can't pass null parameter");
		}
		// Xiaohui Maybe not correct, since b is 1 and a should be different in
		// order to intersect; Checkcheck!
		double det = this.a * segment2.b - this.b * segment2.a;
		if (Math.abs(det) < Constants.EPS) {
			if (this.isOnSegment(segment2.start)) {
				return true;
			}
			if (this.isOnSegment(segment2.end)) {
				return true;
			}
			if (segment2.isOnSegment(this.start)) {
				return true;
			}
			if (segment2.isOnSegment(this.end)) {
				return true;
			}
			return false;
		}

		double x = -(segment2.b * this.c - this.b * segment2.c) / det;
		double y = -(-segment2.a * this.c + this.a * segment2.c) / det;

		Point2D p = new Point2D.Double(x, y);
		if (!this.isOnSegment(p)) {
			return false;
		}
		if (!segment2.isOnSegment(p)) {
			return false;
		}
		return true;
	}

	/**
	 * Returns distance from the specified point to a closest point on the
	 * segment. In case the closest point on the segment's line does not fall
	 * inside the segment the distance to a closest segment vertex is returned.
	 * 
	 * @param point
	 *            the specified point
	 * @return distance from the specified point to a closest point on the
	 *         segment
	 */
	public double getDistanceToPoint(Point2D point) {
		double ax = point.getX() - this.start.getX();
		double bx = this.end.getX() - this.start.getX();
		double ay = point.getY() - this.start.getY();
		double by = this.end.getY() - this.start.getY();

		double denom = bx * bx + by * by;
		if (denom < Constants.EPS) {
			return this.start.distance(point);
		}

		double t = (ax * bx + ay * by) / denom;
		if (t < 0) {
			return this.start.distance(point);
		}
		if (t > 1) {
			return this.end.distance(point);
		}
		double x = this.start.getX() * (1.0 - t) + this.end.getX() * t;
		double y = this.start.getY() * (1.0 - t) + this.end.getY() * t;

		double dx = x - point.getX();
		double dy = y - point.getY();

		return Math.sqrt(dx * dx + dy * dy);
	}

}
